Chapter 1:
Introduction

Once again, welcome back. In the last lesson, we looked at how to use arrays; we used several classes (Player, Team, and TeamDriver) working together to achieve a result; and we covered two more loop formats, one of which is especially useful in managing collections.

In today's lesson, we're going to look at two more of Java's most useful processes: writing and reading files. The team information we compiled in the last lesson was useful, but it would be very tedious if we had to enter it by hand each time we wanted to use it. It's also inconvenient to make the data part of the program because then we have to recompile it every time we want to use different data.

Most data in computer systems is stored in files on disks or other media for several reasons. We can make stored data available to more than one program, send it across networks (and the Internet), keep it more secure, and easily edit and maintain it.

Since using stored data is such a common practice, we're going to learn how to create a disk file with our data in it, as well as how to read a disk file that contains data we want to use. There are (as always, it seems) multiple ways to write and read data with Java programs, so we'll start with the simplest way: writing and reading text files.

Let's get started!

Chapter 2:
Files

Data files are a simple concept, but because people are constantly finding new ways to use and improve on them, they're still a very big topic. Files are simply data that is organized in a recognizable, reusable way and stored on some external media. We use files for an endless variety of purposes: for storing pictures from digital cameras; for sound files, like MP3 and WAV formats; for video, like on YouTube; and for databases that store incredible amounts of data for businesses and other organizations. But all those files use formats that are very complex.

We're going for a much simpler scheme—one that's used on practically every computer in existence. The process we'll use simply writes text to a file in a format we can read again. Even considering text in a file requires some planning, though, since finding the following text in a file probably wouldn't be very useful to most of us:

#gbarl{border-top:1px solid #c9d7f1;font-size:0;position:absolute;width:110%}
        

This is one line of CSS code that would be stored in a file and used by a web page for styling. The colons and semicolons within the text act as delimiters, separating different parts of the text so the computer can break it apart and process the text accordingly. When designing your own data text files, you also need to use delimiters to organize the text in a way that's easy for a computer to break apart into smaller chunks.

There are a number of formats used for text files that contain data. Before we write a file, we need to make sure that everyone who might want to use it agrees on the format. And if we want to read an existing file, we'll need to find out its format before we can successfully read it in a program. Let me explain what I mean by format a little further.

A text file that contains data is usually divided into records, with one record on each line of the file. A record is a group of related data fields containing information about one of the entities in the file. For example, if we used data on the players from the team project we did in the last lesson, each record would hold information about one of the players.

We also need to have a record format that matches the file we want to use. This helps us know what information is in each record and where it is in the record. Without a record format, you wouldn't know what information the following line contains:

John Doe C 19.19 15.15 4.4
        

It's not easy to tell that this is the player information for one of the players from the exercise in the last lesson. But if we knew the record format, we would know that John Doe is the player's name, he's a center, and he averages 19 points per game. So it's important to have a record format that tells us what information is in each record of the file.

We'll also need to know what separator character the file uses between fields in a record, if any. Let me show you some of the more popular ways to store the same information with different separator characters:

John Doe C 19.19 15.15 4.4
		John,Doe,C,19.19,15.15,4.4
		John        Doe         C19.1915.15 4.4
        

The first line uses a blank character as a separator. The second uses a comma. The third does not have a separator at all but expects each data item to start in a certain location in the record. The first name, for example, can be up to 12 characters long, followed by 12 more for the last name. The player's position is in the next column (column 25). The average points per game for the player is next, in column 26, and takes five columns, including a decimal point. After that is average rebounds, and last is average assists.

You can see that there are a number of text file formats. So before we write or read them, we need to make sure we have the format correct. For this lesson, since none of our fields will contain blanks, we'll use the first format above, with a space between fields in a record. We could have just as easily used one of the others, since they all work equally well. But I had to choose one of them!

Declaring Files in Java

As you might suspect, if we're going to use a file in a Java program, we have to tell Java about it. To do that, we have to declare the files in our program, just like any other data object we want to use.

TQA-7 --- We'll start by going over how to declare an output text file. Java provides a convenient class for us to use when declaring an output text file. It's called the PrintStream class. This class does text output in a way that's very similar to displaying text on your screen. There are other ways to write output files, but for learning purposes, this class is the easiest way to start.

TQA-8 --- Before we can use the output stream, we have to declare it like this:

PrintStream outputFile = new PrintStream("FileName.txt");
        

This declaration creates a variable, outputFile, that we'll use in our program to refer to the file. The string FileName.txt represents the name of the physical file on the disk. So if we wanted to create a file named NewFile.txt on our disk, we would use this declaration:

PrintStream outputFile = new PrintStream("NewFile.txt");
        

I need to mention where Java creates and looks for files. If you use an unqualified name like I just did, Java will look for (or create) the file in the same disk directory the program file is in. If you want to create your file in another directory or on another disk, you need to put the full path into the name, like this:

PrintStream outputFile = new PrintStream("D:\\NewFile.txt");
        

This will create a file named NewFile.txt in the root directory of the D: drive. Notice that I put two backslashes (\\) where there would normally be one in the file path. That's because in Java, you have to use two backslashes to represent one inside a character string. If Java finds a single backslash in a string, it uses the next character to try to create an escape character, a character with a special meaning. For example, Java treats "\n" as a line return and "\t" as a tab character.

Okay, let's get off this rabbit trail and back to the topic of creating a file. We've declared the file so that Java knows how to use it. Now we need to actually write data to it in the format we said we would. That means we need to create a character string from a player that represents one line in the file. I've modified my Player class to do that, so let me show it to you, then I'll explain the changes I made. Here is my new Player class:


        public class Player
		{
			private String name;
		    private char position;
			private double avgPoints;
			private double avgRebounds;
		    private double avgAssists;



public Player(String pName, char pPos, double pPoints, double pRebounds, double pAssists) { name = pName; position = pPos; avgPoints = pPoints; avgRebounds = pRebounds; avgAssists = pAssists; }


public String toString() { return "Player: " + name + "\n Position: " + position + "\n Points/Game: " + avgPoints + "\n Rebounds/Game: " + avgRebounds + "\n Assists/Game: " + avgAssists; } public String toFile() { return name + " " + position + " " + avgPoints + " " + avgRebounds + " " + avgAssists; } }

I added a new method I named toFile() to the class. The purpose of this method is to format the player's data for output to a file in the format we laid out earlier: consecutive fields with spaces between them.

That gives us a string to use for each line of our output file.

To see how we write the strings to the file,
we'll look at our other classes, Team and TeamDriver.

Chapter 3:
The Team Class

I've also updated the Team class to supply a formatted string for an output file. I made a few other changes while I was at it to simplify the class. Before I describe the changes, let me give you the new class so you have it to look at when I go over them. Here it is:

import java.util.ArrayList;



public class Team { private ArrayList<Player> roster; // declare array for roster

public Team() { roster = new ArrayList<Player>(); // create array for roster }

public void addPlayer(Player player) { roster.add(player); // add player to roster }

public String toString() { String teamRoster = "Team Roster\n\n"; // output String

for (Player p : roster) // for each player in roster { teamRoster = teamRoster + p.toString() + "\n"; // add name to roster }

return teamRoster; // return roster }

public String toFile() { String fileRoster = ""; // output String

for (Player p : roster) // for each player in roster { fileRoster = fileRoster + p.toFile() + "\n"; // add name to roster }


return fileRoster; // return roster } }

This class looks quite a bit different than it did before. I made some changes to get rid of some difficulties associated with using arrays. For example, when you're using an array, you have to tell Java how big it should be before you can use it. If you guess wrong and need more space, it's a pain because you can't expand an array. If you made the array too big, you wasted space.

Another problem with using an array is that we had to keep track of the number of players ourselves to know when we reached the end of our array. And for the toString() method to work properly, the array size had to match the number of players exactly.

Well, there's a way to get Java to do all the hard work for us with arrays. There is a class in the Java class library that acts like an array, with some additional benefits. It lets us store a bunch of similar items like an array does, it keeps track of how many items are stored, and it even grows automatically if we need more space than we said in the first place.

You might have already guessed from the import statement at the start of the chapter that the class I'm referring to is the ArrayList class. I added that statement so I could create an ArrayList object in my program.

ArrayList is one of a group of classes in Java that are called collections. These classes give us different ways to organize groups of things. Some examples of Java collections are lists, sets, and maps. We'll see more of these in future lessons. But I don't want to get too far off track right now, so we'll save those for later. Right now, let's just look at how to use the ArrayList and how it makes our class simpler.

The first thing in the Team class now is a declaration of the ArrayList variable roster. Part of the ArrayList declaration is the type of items we want to put in it, Player items. The item type is put in angle brackets (<>) to keep it separate from the variable name. In short, this line tells Java that we want to declare an ArrayList object named roster that will hold Player items.

Next, be sure to notice that I did not have to declare a variable to keep track of the team size. The ArrayList will do it for us. Any time we want to know how many items are in our list, all we need to do is call the ArrayList's size() method. Since our ArrayList is named roster, we can call its size() method like this:

roster.size()
        

The constructor now only needs one line to allocate the new ArrayList object and set our variable roster to refer to it. I deleted the statement that initialized the team size, since the ArrayList takes care of it for us.

The addPlayer() method doesn't need to worry about trying to put too many players into the array anymore. The ArrayList will expand if it needs to. So all this method does is call the ArrayList's add() method to add a player.

One nice thing about the for-each loop we used last time is that it works exactly the same for an ArrayList as it does for an array, so we don't need to change our toString() method at all!

The last change I made to this class is to add a new method named toFile(). Its purpose is to convert the team to a string in our file format, just like the toString() method built a string in display format. The new method looks pretty similar to toString(). The only differences are that it starts with an empty string instead of starting with a display heading. Then we add a new line to the string for each player on the team by calling the Player's toFile() method instead of its toString() method.

That takes care of the Team class. The last thing left to do is to change the TeamDriver class to write an output file.

The TeamDriver Class

There are fewer changes to this class than the last one, so there's not much left to this chapter. (I think I hear cheering out there!) As before, here's the class, then we'll go over the changes:

 
        import java.io.*;
		public class TeamDriver
		{
			public static void main(String[] args)
			{
				Team t = new Team();
				PrintStream oFile;
                
                

t.addPlayer(new Player("Jane Doe",'C',19.19,15.15,4.4)); t.addPlayer(new Player("Jill Spratt",'F',11.11,7.7,3.3)); t.addPlayer(new Player("Emily Hall",'G',13.13,4.4,6.6)); t.addPlayer(new Player("Daisy Duck",'G',8.8,2.2,1.1)); t.addPlayer(new Player("Minnie Mouse",'C',5.5,1.1,2.2)); t.addPlayer(new Player("Petunia Pig",'G',9.9,3.3,6.6)); t.addPlayer(new Player("Mother Goose",'F',8.0,4.0,2.0));

System.out.println(t.toString());

try { oFile = new PrintStream("TeamFile.txt"); oFile.print(t.toFile()); oFile.close(); } catch(IOException e) { System.out.println("*** I/O Error ***\n" + e); } } }

The first change is the first line, where we need to import Java's java.io package to be able to do file input and output. This package contains a number of classes that are used for different types of input and output.

The next change is the eighth line, where we declare our output file, a PrintStream I named oFile.

The data also changed slightly, in that none of the players have three names now, like "Rip Van Winkle" did. To make our input file work later, each player now has two names, as we will be looking for two name strings for each player when we read the file.

The lines that create and display the players didn't change at all.

The rest of the program creates and writes the output file. For now, don't worry too much about the try and catch keywords other than to know why they are there. Here's a brief explanation: Almost any command dealing with files can cause errors because input and output are error-prone processes. For example, what if you try to read a file that's not there? Or write a file that already exists? Or specify the wrong directory? And on and on. So Java forces us to deal with these errors in try and catch blocks.

The statements that can cause output errors go in the try block. The catch blocks— there can be several—tell Java what types of errors (exceptions) to watch for and what to do if one is found. In our case, the catch block tells Java to look for input and output errors, which Java creates as IOException objects. If an IOException occurs (or gets thrown, in Java terminology), our catch block will catch it and display the *** I/O Error *** message I set up. That's all I'll say about these right now, I promise. For now, just put these in your program the same way I did.

The actual file statements are easier than the whole try-and-catch setup. The first command in the block creates the file using the statement format we discussed at the start of the lesson. In this case, I named my file TeamFile.txt and put it in the same directory as the program.

The second line uses the print() method from the PrintStream class, but it works exactly like the method we have been using for output to the screen already, so that should make sense. This command writes the output string from the Team object to the file with all the team's data in it.

The third line is very important when dealing with files. It closes the file, which means that it makes sure the end of the output file is written properly. Output files need to be closed properly or we'll get errors when we try to read them. So be sure to run the close() method every time you finish an output file.

Now we're finally done writing our file! When we compile these classes and run the driver program, we get a window that looks like this:



Team roster display

We also get an output file named "TeamFile.txt". When I open it in my text editor, this is what I see:



Team roster output file

I hope that's what you got, too.
If not, please let me know in the Discussion Area and we'll figure out what went wrong.

Chapter 4:
Reading an Input File

Now for the last topic in today's lesson! We have a file on disk with the team's data in it. If we want to print a roster from it or do anything else with the information, how do we read it and use it? We won't have to change the Player and Team classes this time. But we will need a new TeamDriver program.

Once again, let me show you the class, then I will explain what it does:


 import java.io.*;
		import java.util.Scanner;



public class TeamDriver { public static void main(String[] args) { Team t = new Team(); String name; char position; double avgPoints; double avgRebounds; double avgAssists;

try { Scanner scan = new Scanner(new File("TeamFile.txt")); while (scan.hasNext()) { name = scan.next() + " " + scan.next(); position = scan.next().charAt(0); avgPoints = scan.nextDouble(); avgRebounds = scan.nextDouble(); avgAssists = scan.nextDouble(); t.addPlayer(new Player(name, position, avgPoints, avgRebounds, avgAssists)); }

scan.close(); } catch(IOException e) { System.out.println("*** I/O Error ***\n" + e); }


System.out.println(t.toString()); } }

Since we'll be creating a team of players from values we get out of a file, we'll need local variables to hold those values. So the first thing we need to do is declare our local variables. In this example, I used the name t for our Team and the names name, position, avgPoints, avgRebounds, and avgAssists for the information we need to create a Player. As you will see, we won't actually need a name for our Player objects.

Since we're using file operations, we need to have try and catch blocks again in case there are input errors. Inside the try block, you can see that we're able to use the same Scanner class we've used for keyboard input to process input files as well. Instead of creating our Scanner with an argument of System.in, we create it with a File object as its argument. Since we don't need to use the File object outside of the Scanner class, we can create it without a name at the same time we pass it to the Scanner. So the argument for the Scanner is new File("TeamFile.txt"), where the argument we pass to the File constructor is the name of the disk file we want to read. In this case, we're assuming that the file is in the same disk directory as our program.

Once we've created and connected the Scanner and File objects, we can read data from the file just as if it were being entered from the keyboard. We have a loop set up that keeps reading as long as there is more data to read. Each time through the loop, we read one line from the input file with information about one player.

As we read each player's information from the file, we create a new Player object to hold the data, and add the object to our team in the Team object t.

Once we finish processing the input file, we close the Scanner object to release the file properly.

Our last action in this program is to display the team roster the same way we did before using t's toString() method and a call to println(). If reading the file worked as it should, you should see the following output on your screen:



Roster created from an input file

Note that this is exactly the same roster we saw when we created the file in the driver program.

Since we wrote that information to a file, then read it back in to create a roster of players again, we should expect to see the same results.

Chapter 5:
Summary

That's our lesson for today. We learned how to use classes from Java's java.io package to read and write text files to and from disks (or other devices) connected to our computers. One of the most common tasks we will encounter in programming is loading data from a file or creating a file with data we have processed. So if you have any trouble at all making it work, or don't understand why it works, please let me know in the Discussion Area. We'll figure it out.

In our next lesson, we'll take a deeper look at how Java uses inheritance. Object-oriented languages use inheritance as a mechanism that allows one class to reuse the features of another without having to recreate those features. It's one of the distinctions of the object-oriented paradigm, and we'll see how to make it work for us next time.

Until then, have a great time playing with files, and don't forget to complete the assignment and take the quiz for this lesson!

I'll talk to you later.

Lesson 3 FAQs

Q: Does Java provide database input and output capabilities as well as file input and output?

A: Yes, the Java package java.sql contains a group of classes that provide relational database functions. There is not enough time to address them in this course, though. There's a tutorial available from Sun at http://java.sun.com/docs/books/tutorial/jdbc/basics/index.html .

Lesson 3 Assignment


For this lesson, I'd like you to read a text file I've included with this assignment. The text file contains some simple program code and comments. (You could also use a file of your own creation.)

Download Lesson 3 Assignment 3 text file


For this exercise, we'll assume that all comments begin with two slashes (//) and that the slashes are in the first two positions of the line. Write a program to read the input file, find all the lines with comments, and write the comments to an output file. The net result will be a file containing just the comment lines from the input file.

To do this exercise, use the Scanner class nextLine() method to read a complete line at a time. Put the line into a String variable, and check the first two characters to decide whether to write the line to the comment file. The method indexOf() in the String class gives you an easy way to check for one string in another string and find its location.

Once you've completed the assignment, click the link below to see a sample solution:

Lesson 3 Assignment Solution 1


If you want to go a step further, have your program create two output files. The first should be the same as above, containing all the comment lines from the input file. The second should contain all the other lines—the ones that are not comments.

Lesson 3 Assignment Solution 2


If you want another challenge, try finding the comments that do not start in the first column using the String class indexOf() method and write them to the comment file, too.

Lesson 3 Assignment Solution 3


Let me know in the Discussion Area if you have any questions about this assignment.

Lesson 3 Quiz Answers

1.Which of the following is a valid file format?
MP3.

2. What is a record in a file?
A group of related data fields containing information on one of the entities in a file.

3. What does a record format provide?
The sequence and formats of the fields in a record.

4. What class (or classes) did we use to create an output file?
The PrintStream class.

5. What class (or classes) did we use to read an input file?
The File and Scanner classes.

Lesson 3 Supplementary Materials